/**
 * mLibRay3.js
 * Created: May 2013
 * Source link: http://code.google.com/p/code-depository/source/browse/
 *
 * By Suresh Nalluri,
 * License: The MIT License (MIT)
 *          See LICENSE details
 *
 *
 *	Todo:
 *		transform
 *
 * Requires:
 *	mLibFactory.js
 *	mLibIncludes.js
 *	mLibVec3.js
 *	mLibLineSegment.js
 *	mLIB.Quaternion,
 *  mLIB.Mat44;
 *
 *
 * 3D Ray Class
 * A Ray object is composed of an anchor point (or Origin) and a normalised Direction vector.
 * Unlike a Line Segment (Lin3Seg), the ray extends only in the direction of the direction vector.
 * note the components themselves are objects
 *
 *
 * note this constructor pattern allows us to create objects in 2 ways:
 * 1. var o = mLib.Ray3( 0 or more params );      //OR
 * 2. var o = new mLib.Ray3( 0 or more params );
 *
 * NOTE: var o = [new] mLIB.Ray3(<no params>); // will return default line composed of the
 * unit vector in direction i (1,0,0), and the Origin (0,0,0) as the starting point.
 *
 *
 * added benefit of this pattern is that we get a copy constructor by default!!
 * eg. var newRay = mLIB.Ray3(existingRay);
 * so no need to have an explicit copy method.
 *
 * Usage:
 *		var l = [new] mLIB.Ray3([origin, P | line | lineSeg | Ray |  <noargs> ]);
 * 
 *-------------------------------------------------------
 **/
 

if (typeof mLIB == 'undefined') function mLIB() {};

( function(module) {
	var Lin3 = module.Lin3,
        Lin3Seg = module.Lin3Seg,
        Ray3 = module.Ray3,
        Mat44 = module.Mat44,
        Vec3 = module.Vec3,
        Point3 = module.Point3;

	module.Ray3 = Factory.obj( {
		init: function() {
		
			if ( !arguments.length ) {
				// we have no params - return defaults
				this.origin = module.Point3(module.Point3.origin);
				this.direction = module.Vec3(module.Vec3.xAxis);
			} else if (arguments[0].isClass(module.Point3) && arguments[1].isClass(module.Point3) ) {
				// we have 2 params - anchor point and a point on the ray.
				// *** note if arg[1] is really a direction vector, we return rubbish as we have
				// *** no way of distinguishing a point from a vector.
				this.origin = module.Point3(arguments[0]);
			  	this.direction = module.Vec3( arguments[1].Minus(arguments[0]) ).normalise();
				
			} else if ( arguments[0].isClass(module.Lin3Seg) )  {
				// we have 1 param - a Line segment; we need to normalise the direction vector for a Ray
				this.origin = module.Point3(arguments[0].origin);
				this.direction = module.Vec3( arguments[0].direction ).normalise();
				
			} else if ( arguments[0].isClass(module.Lin3) || arguments[0].isClass(module.Ray3)) {
				// we have 1 param - a Line or Ray object .. no ned to call normalise
				this.origin = module.Point3(arguments[0].origin);
				this.direction = module.Vec3( arguments[0].direction );
			
			} else {
				// we have unknown type params - return defaults
				this.origin = module.Point3(module.Point3.origin);
				this.direction = module.Vec3(module.Vec3.xAxis);
			}

		}, // init()

		// apply transform to ray - see Lin3.transform for notes on efficiency
		// requires the Quaternion and Mat44 libs
        transform: function(scale, rQuat, translateVec) {
            var origin, direction,
                transOp;
            
            // form the transformation matrix from the quaternion and the scale factor
            // note this is probably not the most efficient way but at least it doesn't
            // break any oo rules.
            transOp = new Mat44(rQuat).multiply(Mat44().scaling(Vec3(scale, scale, scale));

            // transform the direction;
            direction = transOp.Transform(this.direction).normalise();

            // apply the translation vector to the matrix
            // again note: not the most efficient way of doing it, but no oo rules broken
            transOP.addTranslation(translateVec);

            // transform the origin
            origin = transOp.Transform(this.origin);
            
            return Ray3(origin, direction);

        },	// transform()


 		// equality tests
		isEqual: function( ray ) {
			return ( this.origin.isEqual(ray.origin) && this.direction.isEqual(ray.direction) );
		},
		
		notEqualTo: function ( ray ) {
			return !( this.origin.isEqual(ray.origin) && this.direction.isEqual(ray.direction) );
		},

		distanceSquared: function( e) {
			if ( e.isClass(module.Ray3) ) {
				// between this ray R1 and another R2
				return disSqRayRay(this, e);
			}
			else if ( e.isClass(module.Lin3) ) {
				// between this ray R and a line L
				return disSqRayLine(this, e);
			}
			else if ( e.isClass(module.Point3) ) {
				// between this ray R and a point Q
				return disSqRayPoint(this, e);
			}
			
			// unknown type parameter
			return null;
		},


		// closest point on this ray to a point
		ClosestPoint: function(point) {
			var w = point.Minus(this.origin),
				// vsq = this.direction.dot(this.direction), //should always be 1 since direction is a unit vector
				prj = w.dot(this.direction);	// w.v

			if ( prj <= 0.0 ) {
				// the starting point P0 of this ray is closest
				return this.origin.Copy();
			} else {
				// P0 + (prj/vsq)*Ld, but since vsq=1, we simplify to P0 +prj*Ld
				return this.direction.ScaleUp(prj).plus(this.origin);  // Po + (prj*/vsq)*Ld
			}
			
		},	// ClosestPoint()

        // get closest points between this ray R and E ( another ray or a line)
		ClosestPoints: function(e) {
			// 1. between 2 rays R1 and R2
			// 2. between a Ray R and a Line L
			// 3. between a Line L and a Ray R (same as 2 but params swapped

			if ( e.isClass(module.Ray3) ) {
				// between 2 rays
				return closestPointsRayRay(this, e);
			}
			else if ( e.isClass(module.Lin3) ) {
				// between this ray and a line
				return closestPointsRayLine(this, e);
			}

		},
		
        // accessor and setter methods
        get: function(out) {
			// note we're not returning copies
			out.origin = this.origin;
			out.direction = this.direction;
			return this;
		},
        
        getDirection: function() {
			// note we're not returning copies
			return this.direction;
		},

		set: function(origin, direction) {
			this.origin = module.Point3(origin);
			this.direction = module.Vec3(direction).normalise();
			return this;
		},
		
		// debug
		log: function() {
			return ['{ o:', this.origin.log(), ', d:', this.direction.log(), '}'].join('');
		},

		toArray: function() {
			return [this.origin.toArray(), this.direction.toArray()].join('');
		}
	} ); // module.Lin3 = Factory.obj

	/**
	 * internal helper functions.
	 **/
	 function disSqRayRay(ray1, ray2) {
		var sC, sCn, sCd, tC, tCn, tCd, wc,
			w0 = ray1.origin.Minus(ray2.origin),
			a = ray1.direction.dot(ray2.direction),	// u.v
			// b = ray2.direction.dot(ray2.direction) should always be 1 since R2d is the unit direction vector
			// c = ray1.direction.dot(ray1.direction) should always be 1 since R1d is the unit direction vector
			d = w0.dot(ray2.direction),				// w0.v
			e = w0.dot(ray1.direction),				// w0.u
			denom = a*a - 1.0;		// denom = a*a-b*c; but since  b=c=1, simplify to denom = a*a-1;

		// Note.	sC = sCn / sCd
		//			tC = tCn / tCd

		if ( module.Includes.isZero(denom) ) {
			// parallel segments, as per parallel lines we start with sC set to 0
			sCd = tCd = 1.0; // = b;
			sCn = 0.0;
			tCn = d;
		} else {	// restrict sC to [0,1]
			
			sCn = e - a*d;	// sC=(eb–ad)/(a^2–bc ), but since b=1; we simplify to sC=(e–ad)/(a^2–1)
			tCn = e*a - d;	// tC=(ea–dc)/(a^2–bc ), but since c=1; we simplify to tC=(ea-d)/(a^2–1)
			sCd = tCd = denom;
			
			// if ( sCn < 0.0 ) {		// restrict sC to 0
			if ( (sCn < 0.0 && sCd > 0.0) || (sCn > 0.0 && sCd < 0.0) ) {
				// restrict sC to 0
				sCn = 0.0;
				tCn = d;
				tCd = 1.0; 	// = b;
			}
		}

		// restrict tC to >= 0
		// if ( tCn < 0.0 ) {	// restrict tC to 0
		if ( (tCn < 0.0 && tCd > 0.0) || (tCn > 0.0 && tCd < 0.0) ) {
			// restrict tC to 0
			tC = 0.0;
			//sC = –e/c but c=1, so simplify to sC = -e
			// restrict sC to >= 0
			sC = -e < 0.0 ? 0.0 : -e;
			
		} else {
			sC = sCn / sCd;
			tC = tCn / tCd;
		}

		// get wc = w0 + sC*R1d - tC*R2d
		wc = w0.plus(ray1.direction.ScaleUp(sC)).minus(ray2.direction.ScaleUp(tC));
		return wc.lengthSquared();	// same as wc.wc ie. wc.dot(wc)

	 } // end of helper disSqRayRay();
	 
	 function disSqRayLine(ray, line) {
		var sC, sCn, tC, wc,
			w0 = ray.origin.Minus(line.origin),
			a = ray.direction.dot(line.direction),	// u.v
			b = line.direction.dot(line.direction),	// v.v
			// c = ray.direction.dot(ray.direction)should always be 1 since Rd is the unit direction vector
			d = w0.dot(line.direction),				// w0.v
			e = w0.dot(ray.direction),				// w0.u
			denom = a*a - b;	// denom = a*a-b*c; but since  c=1, simplify to denom = a*a-b;

		// Note.	sC = sCn / sCd
		//			tC = tCn / tCd

		if ( module.Includes.isZero(denom) ) {
			// parallel lines
			// sC = 0.0; tC = d/b
			wc = w0.minus(line.direction.ScaleUp(d/b));
		} else {
			sCn = e*b - a*d;
			
			//restrict sC to [0,1]
			//if ( sCn < 0.0 ) {
			if ( (sCn < 0.0 && denom > 0.0) || (sCn > 0.0 && denom < 0.0) ) {
				// sC < 0.0, set it 0
				sC = 0.0;
				tC = d/b;
			//} else if ( sCn > denom ) {
			} else if ( (sCn > 0 && denom > 0 && sCn > denom) || (sCn < 0 && denom < 0 && sCn < denom) ) {
				// restrict sC to 1  
				sC = 1.0;
				tC = (a+d) / b;
			} else {
				sC = sCn / denom;
				tC = (e*a - d) / denom; // tC=(ea–dc )/(a^2–bc ), but c=1, so we have tC=(ea–d )/(a^2–b )

			}

			// wc = w0 + sC*Rd - tC*Ld
			wc = w0.plus(ray.direction.ScaleUp(sC)).minus(line.direction.ScaleUp(tC));
		}

		return wc.lengthSquared();	// same as wc.wc
		
	 } // end of helper disSqRayLine()

	 function disSqRayPoint(ray, point) {
	 	var w = point.Minus(ray.origin),
			prj = w.dot(ray.direction);		// w.v
		
		if ( prj <= 0 ) {
			// restrict tC to 0 
			return w.lengthSquared();	// same as w.dot(w)
		} else {
			// ||n||^2
			// w.w - (w.v)^2 / v.v, but v is the unit direction vector of the Ray ==>  v.v = 1!
			// ==> w.w-(w.v)^2
			return ( w.lengthSquared() - prj*prj );
		}
	 } // end of helper disSqRayPoint()
	 
	 // get closest points between 2 rays
	 function closestPointsRayRay(ray1, ray2) {
		var sC, sCn, sCd, tC, tCn, tCd,
			w0 = ray1.origin.Minus(ray2.origin),
			a = ray1.direction.dot(ray2.direction),	  // u.v
			// b = ray2.direction.dot(ray2.direction) // v.v should always be 1 since v is the unit direction vector of ray2
			// c = ray1.direction.dot(ray1.direction) // u.u should always be 1 since u is the unit direction vector of ray1
			d = w0.dot(ray2.direction),				// w0.v
			e = w0.dot(ray1.direction),				// w0.u
			denom = a*a - 1.0;	// denom = a*a-b*c; but since  b=c=1, simplify to denom = a*a-1;

		// Note.	sC = sCn / sCd
		//			tC = tCn / tCd

		if ( module.Includes.isZero(denom) ) {
			// parallel segments, as per parallel lines we start with sC set to 0
			sCd = tCd = 1;  // = b;
			sCn = 0.0;
			tCn = d;
		} else {	// restrict sC to >=0
			sCn = e - a*d;	// sC=(eb–ad)/(a^2–bc), but b=c=1, so we have sC=(e–ad)/(a^2–1)
			tCn = e*a - d;	// tC=(ea–dc)/(a^2–bc), but b=c=1, so we have sC=(ea–d)/(a^2–1)
			sCd = tCd = denom;
			
			// if ( sCn < 0.0 ) {		// restrict sC to 0
			if ( (sCn < 0.0 && sCd > 0.0) || (sCn > 0.0 && sCd < 0.0) ) {
				// sC < 0.0 restrict sC to 0
				sCn = 0.0;
				tCn = d;
				tCd = 1; // = b
			}
		}

		// restrict tC to >= 0
		// if ( tCn < 0.0 ) {	// restrict tC to 0
		if ( (tCn < 0.0 && tCd > 0.0) || (tCn > 0.0 && tCd < 0.0) ) {
			// tC < 0.0 restrict tC to 0
			tC = 0.0;
			
			//sC = –e/c but c=1, so simplify to sC = -e
			// restrict sC to >= 0
			sC = -e < 0.0 ? 0.0 : -e;

		} else {
			tC = tCn / tCd;
			sC = sCn / sCd;
		}

		// closest points !!
		return {
			Pc: ray1.direction.ScaleUp(sC).plus(ray1.origin),// Po + sC*R1d
			Qc: ray2.direction.ScaleUp(tC).plus(ray2.origin)	// Qo + tC*R2d
		};

	 } // end of helper closestPointsRayRay()

    function closestPointsRayLine(ray, line) {
		var sC, sCn, tC, wc,
			w0 = ray.origin.Minus(line.origin),
			a = ray.direction.dot(line.direction),	// u.v
			b = line.direction.dot(line.direction), // v.v
			// c = ray.direction.dot(ray.direction),	// u.u should always be 1 since Rd is the unit direction vector
			d = w0.dot(line.direction),				// w0.v
			e = w0.dot(ray.direction),				// w0.u
			denom = a*a - b;		// denom = a*a-b*c; but since  c=1, simplify to denom = a*a-b;

		// Note.	sC = sCn / sCd
		//			tC = tCn / tCd

		if ( module.Includes.isZero(denom) ) {
			// parallel lines, let's just return P0 from segment & point Qc on line closest to P0
			// compute closest points
			// tC = d/b
			return { 
				Pc: ray.origin.Copy(),
				Qc: line.direction.ScaleUp(d/b).plus(line.origin) // L1o + tC*L1d
			};
		} else {
			// sC=((e*b-a*d)/denom)
			sCn = e*b - a*d;
			
			//restrict sC to >=0
			if ( (sCn < 0.0 && denom >0.0) || (sCn >0.0 && denom < 0.0) ) {
				// sC < 0, set sC = 0
				sC = 0.0;
				tC = d/b;
			} else {
				sC = sCn / denom;
				tC = (e*a - d) / denom;	// tC=(e*a-d*c)/denom, but c = 1, so tC=(e*a-d)/denom
			}

			return {
				Pc: ray.direction.ScaleUp(sC).plus(ray.origin),	// Ro + sC*Rd
				Qc: line.direction.ScaleUp(tC).plus(line.origin)	// Lo + tC*Ld
				
			};
		}
	 
	 } // end of helper closestPointsRayLine()


    // friend methods ---------------------------------------------
    //
    var friends = {
	
		// distance squared between 2 entities
		distanceSquared: function( e0, e1 ) {
			// 1. between 2 rays R1 and R2
			// 2. between a Ray R and a Line L
			// 3. between a Line L and a Ray R (same as 2 but params swapped
			// 4. between a Ray R and a point Q
			// 5. between a point Q and a Ray R (same as 4 but params swapped

			if ( e0.isClass(module.Ray3) && e1.isClass(module.Ray3) ) {
				return disSqRayRay(e0, e1);
			}
			else if ( e0.isClass(module.Ray3) && e1.isClass(module.Lin3) ) {
				return disSqRayLine(e0, e1);
			}
			else if ( e0.isClass(module.Lin3) && e1.isClass(module.Ray3) ) {
				return disSqRayLine(e1, e0);
			}
			else if ( e0.isClass(module.Ray3) && e1.isClass(module.Vec3) ) {
				return disSqRayPoint(e0, e1);
			}
			else if ( e0.isClass(module.Vec3) && e1.isClass(module.Ray3) ) {
				return disSqRayPoint(e1, e0);
			}
		},
		
		closestPoints: function(e0, e1) {
			// 1. between 2 rays R1 and R2
			// 2. between a Ray R and a Line L
			// 3. between a Line L and a Ray R (same as 2 but params swapped

			if ( e0.isClass(module.Ray3) && e1.isClass(module.Ray3) ) {
				return closestPointsRayRay(e0, e1);
			}
			else if ( e0.isClass(module.Ray3) && e1.isClass(module.Lin3) ) {
				return closestPointsRayLine(e0, e1);
			}
			else if ( e0.isClass(module.Lin3) && e1.isClass(module.Ray3) ) {
				return closestPointsRayLine(e1, e0);
			}

		},

    };  // end of friends{}

    for (var f in friends) {
        friends.hasOwnProperty(f) && (module.Ray3[f] = friends[f]);
    };

} )(mLIB); 	// function(module){}
